﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Threading;
using System.Reflection;

namespace MSAS.DIP.Common
{
    public class FileTransferer
    {
        #region Public Interface

        /// <summary>
        /// Transfer a file from srcPath to destPath
        /// </summary>
        /// <param name="srcPath">Source file name</param>
        /// <param name="destPath">Destination file name</param>
        /// <exception cref="System.UnauthorizedAccessException"/>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.NotSupportedException"/>
        /// <exception cref="System.IO.PathTooLongException"/>
        /// <exception cref="System.IO.DirectoryNotFoundException"/>
        /// <exception cref="System.IO.FileNotFoundException"/>
        /// <exception cref="System.IO.IOException"/>
        public void TransferFile(string srcPath, string destPath)
        {
            TransferHelper(srcPath, destPath, false, null);
        }

        /// <summary>
        /// Transfer a file from srcPath to destPath and compute a hash simultaneously
        /// </summary>
        /// <param name="srcPath">Source file name</param>
        /// <param name="destPath">Destination file name</param>
        /// <param name="verifyFileIntegrity">Decide whether or not to read the destination file after copying and comparing the hashes</param>
        /// <param name="algorithm">Algorithm for hash computation</param>
        /// <returns>computed hash value</returns>
        /// <exception cref="System.UnauthorizedAccessException"/>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.NotSupportedException"/>
        /// <exception cref="System.ObjectDisposedException"/>
        /// <exception cref="System.OperationCanceledException"/>
        /// <exception cref="System.IO.PathTooLongException"/>
        /// <exception cref="System.IO.DirectoryNotFoundException"/>
        /// <exception cref="System.IO.FileNotFoundException"/>
        /// <exception cref="System.IO.IOException"/>
        public byte[] TransferAndHashFile(string srcPath, string destPath, bool willVerify, HashAlgorithm algorithm)
        {
            if (algorithm == null)
            {
                throw new ArgumentNullException("algorithm");
            }
            return TransferHelper(srcPath, destPath, willVerify, algorithm);
        }

        /// <summary>
        /// Read destination file, compute hash, verify result against checksum.
        /// </summary>
        /// <returns>True if file is corrupt, false otherwise</returns>
        /// <exception cref="System.UnauthorizedAccessException"/>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.NotSupportedException"/>
        /// <exception cref="System.ObjectDisposedException"/>
        /// <exception cref="System.IO.PathTooLongException"/>
        /// <exception cref="System.IO.DirectoryNotFoundException"/>
        /// <exception cref="System.IO.FileNotFoundException"/>
        public bool IsFileCorrupt(string destPath, HashAlgorithm algorithm, byte[] checksum)
        {
            //
            // Check arguments
            //
            if (checksum == null)
            {
                throw new ArgumentNullException("checksum");
            }
            if (algorithm == null)
            {
                throw new ArgumentNullException("algorithm");
            }
            if (destPath == null)
            {
                throw new ArgumentNullException("destPath");
            }
            if (String.IsNullOrEmpty(destPath))
            {
                throw new ArgumentException("Destination path cannot be null or empty", "destPath");
            }

            //
            // calculate the hash of the written file
            //
            byte[] hash;
            using (FileStream file = File.OpenRead(destPath))
            {
                hash = algorithm.ComputeHash(file);
            }

            //
            // verify that the hashes are the same
            //
            if (hash.Length != checksum.Length)
            {
                return true;
            }

            for (int i = 0; i < hash.Length; i++)
            {
                if (hash[i] != checksum[i])
                {
                    return true;
                }
            }

            return false;
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Transfer a file from srcPath to destPath and compute a hash simultaneously
        /// </summary>
        /// <param name="srcPath">Source file name</param>
        /// <param name="destPath">Destination file name</param>
        /// <param name="retries">Number of times to retry if failures occur</param>
        /// <param name="pauseInMs">Time to pause between retries</param>
        /// <param name="verifyFileIntegrity">Decide whether or not to read the destination file after copying and comparing the hashes</param>
        /// <param name="algorithm">Algorithm for hash computation</param>
        /// <returns>computed hash value</returns>
        /// <exception cref="System.UnauthorizedAccessException"/>
        /// <exception cref="System.ArgumentException"/>
        /// <exception cref="System.ArgumentNullException"/>
        /// <exception cref="System.NotSupportedException"/>
        /// <exception cref="System.ObjectDisposedException"/>
        /// <exception cref="System.OperationCanceledException"/>
        /// <exception cref="System.IO.PathTooLongException"/>
        /// <exception cref="System.IO.DirectoryNotFoundException"/>
        /// <exception cref="System.IO.FileNotFoundException"/>
        /// <exception cref="System.IO.IOException"/>
        private byte[] TransferHelper(string srcPath, string destPath, bool verifyFileIntegrity, HashAlgorithm algorithm)
        {
            byte[] hash = null;

            hash = CopyFile(srcPath, destPath, algorithm);

            // If we had no problems and we have to verify
            if (verifyFileIntegrity)
            {
                if (IsFileCorrupt(destPath, algorithm, hash))
                {
                    throw new InvalidOperationException(String.Format("File tranfer resulted in a corrupt file at location '{0}'.", destPath));
                }
             
            }

            return hash;
        }

        /// <summary>
        /// Copies a file from src to dest, optionally calculating a hash of the file on the way.
        /// </summary>
        /// <param name="src">file to copy</param>
        /// <param name="dest">destination file</param>
        /// <param name="algorithm">hash to calculate en route</param>
        /// <returns>calculated hash</returns>
        private byte[] CopyFile(string src, string dest, HashAlgorithm algorithm)
        {
            byte[] hash = null;

            //
            // if we're not getting a hash, just use File.Copy
            //
            if (algorithm == null)
            {
                File.Copy(src, dest, true);
            }
            else
            {
                using (ReadOnlyCopyFileStream transferStream = new ReadOnlyCopyFileStream(src, dest))
                {
                    hash = algorithm.ComputeHash(transferStream);
                }
            }
            return hash;
        }
        #endregion
    }
}
